package xyz.robinbingo.rbtask.common.aspects;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import xyz.robinbingo.rbtask.common.bean.ResResult;
import xyz.robinbingo.rbtask.common.enums.ResEnum;
import xyz.robinbingo.rbtask.common.exception.CustomException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.lang.reflect.Method;
import java.util.UUID;

/**
 * LogAspect
 *
 * @author Robin
 * @date 2021/6/22 16:15
 **/
@Aspect
@Slf4j
@Component
public class LogAspect {

    @Value("${spring.application.name}")
    private String appName;

    private static final boolean LOG_RESPONSE = true;

    @Pointcut("execution(* xyz.robinbingo.rbtask.web.controller..*.*(..))")
    public void logPointcut() {
    }

    /**
     * 记录请求及返回日志
     * 注意：如果服务抛出异常，则在此兜底返回正常的 ResResult
     *
     * @param point point
     * @return Object
     * @author Robin
     * @date 2019-11-15 14:47
     **/
    @Around("logPointcut()")
    public Object log(ProceedingJoinPoint point) {
        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
        Object[] args = point.getArgs();
        String methodName = "";
        Class<?> returnType = null;
        try {
            Signature signature = point.getSignature();
            Method method = ((MethodSignature) signature).getMethod();
            methodName = method.getDeclaringClass().getName() + "#" + method.getName();
            returnType = method.getReturnType();
        } catch (Exception e) {
            log.warn("[LogAspect.log]:get method error", e);
        }

        // 入参日志
        log.info("[" + appName + "-req]:method=[{}], uuid=[{}], req=[{}]", methodName, uuid, JSONUtil.toJsonStr(args));
        Object result;
        try {
            // 执行方法
            result = point.proceed(args);
            // 返回日志
            if (LOG_RESPONSE) {
                log.info("[" + appName + "-rsp]:method=[{}], uuid=[{}], rsp=[{}]", methodName, uuid, JSONUtil.toJsonStr(result));
            }
        } catch (Throwable throwable) {
            // 错误处理
            result = handleException(uuid, methodName, throwable, returnType);
        }

        return result;
    }

    /**
     * 错误处理
     *
     * @param uuid       uuid
     * @param methodName methodName
     * @param throwable  throwable
     * @param returnType returnType
     * @return java.lang.Object
     * @author Robin
     * @date 2021/9/7 14:54
     */
    private Object handleException(String uuid, String methodName, Throwable throwable, Class<?> returnType) {
        Object result;
        if (throwable instanceof CustomException e) {
            // 自定义异常
            result = generateResult(uuid, methodName, returnType, e.getCode(), e.getMessage());
        } else if (throwable instanceof ConstraintViolationException e) {
            // 参数校验失败
            String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).findFirst().orElse("");
            result = generateResult(uuid, methodName, returnType, ResEnum.ILLEGAL_ARGUMENT.getCode(), message);
        } else {
            result = generateResult(uuid, methodName, returnType, ResEnum.FAIL.getCode(), ResEnum.FAIL.getMessage());
        }
        log.error("[" + appName + "-err]:type=[{}], method=[{}], uuid=[{}], err=[{}]",
                throwable.getClass().getName(), methodName, uuid, ExceptionUtil.stacktraceToString(throwable));
        return result;
    }

    /**
     * generateResult
     *
     * @param uuid       uuid
     * @param methodName methodName
     * @param returnType returnType
     * @param code       code
     * @param message    message
     * @return java.lang.Object
     * @author Robin
     * @date 2021/9/7 14:54
     */
    private Object generateResult(String uuid, String methodName, Class<?> returnType, Integer code, String message) {
        if (ResResult.class.equals(returnType)) {
            return ResResult.fail(code, message);
        } else {
            log.error("unknown returnType,uuid = {} ,methodName = {} ,returnType = {}", uuid, methodName, returnType);
            return null;
        }
    }
}